﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace NavmeshPathfinding
{
    public class Interaction : DrawableGameComponent
    {
        Core core;
        SpriteBatch spriteBatch;
        SpriteFont spriteFont;

        MouseState mState;
        MouseState mStatePrev;
        KeyboardState kState;
        KeyboardState kStatePrev;

        TimeSpan leftButtonLastPressed;
        TimeSpan leftButtonLastClicked;
        bool leftButtonPressed;
        bool leftButtonReleased;
        bool leftButtonClicked;
        bool leftButtonDoubleClicked;

        TimeSpan rightButtonLastPressed;
        bool rightButtonPressed;
        bool rightButtonReleased;
        bool rightButtonClicked;

        bool homeKeyPressed;
        bool f2KeyPressed;
        bool f5KeyPressed;
        bool aKeyPressed;
        bool pageDownPressed;
        bool pageUpPressed;
        bool tabKeyPressed;
        bool plusKeyPressed;
        bool minusKeyPressed;
        bool mKeyPressed;
        bool nKeyPressed;
        bool lKeyPressed;
        bool kKeyPressed;

        List<Agent> selectionTargetList;

        bool displayInformation;
        TimeSpan pathfindingTime;

        int displayInformationLastChecked = 0;
        double fps = 0;

        public Interaction(Core core) : base(core)
        {
            this.core = core;

            mState = new MouseState();
            mStatePrev = new MouseState();
            kState = new KeyboardState();
            kStatePrev = new KeyboardState();
            
            leftButtonPressed = false;
            rightButtonPressed = false;
            leftButtonLastClicked = TimeSpan.Zero;

            selectionTargetList = new List<Agent>();
            displayInformation = true;
            pathfindingTime = TimeSpan.Zero;
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = core.Content.Load<SpriteFont>("hudFont");

            base.LoadContent();
        }

        public static MouseState clone(MouseState s)
        {
            return new MouseState(s.X, s.Y, s.ScrollWheelValue, s.LeftButton, s.MiddleButton, s.RightButton, s.XButton1, s.XButton2);
        }

        public static KeyboardState clone(KeyboardState k)
        {
            return new KeyboardState((Keys[])k.GetPressedKeys().Clone());
        }

        void resetMouseState()
        {
            leftButtonClicked = false;
            leftButtonPressed = false;
            leftButtonReleased = false;
            leftButtonDoubleClicked = false;

            rightButtonClicked = false;
            rightButtonPressed = false;
            rightButtonReleased = false;
        }

        void updateMouseState(GameTime gameTime)
        {
            resetMouseState();

            if (mState.LeftButton == ButtonState.Released)
            {
                leftButtonReleased = true;
                if (mStatePrev.LeftButton == ButtonState.Pressed && leftButtonLastPressed.Add(TimeSpan.FromMilliseconds(Config.Interaction.CLICK_TIMESPAN)).CompareTo(gameTime.TotalGameTime) > 0)
                {
                    leftButtonClicked = true;

                    if (leftButtonLastClicked.Add(TimeSpan.FromMilliseconds(Config.Interaction.DOUBLECLICK_TIMESPAN)).CompareTo(gameTime.TotalGameTime) > 0)
                    {
                        leftButtonDoubleClicked = true;
                    }

                    leftButtonLastClicked = gameTime.TotalGameTime;
                }
            }

            if (mState.LeftButton == ButtonState.Pressed)
            {
                leftButtonPressed = true;
                if(mStatePrev.LeftButton == ButtonState.Released) leftButtonLastPressed = gameTime.TotalGameTime;
            }

            if (mState.RightButton == ButtonState.Released)
            {
                rightButtonReleased = true;
                if (mStatePrev.RightButton == ButtonState.Pressed && rightButtonLastPressed.Add(TimeSpan.FromMilliseconds(Config.Interaction.CLICK_TIMESPAN)).CompareTo(gameTime.TotalGameTime) > 0)
                    rightButtonClicked = true;
            }

            if (mState.RightButton == ButtonState.Pressed)
            {
                rightButtonPressed = true;
                if (mStatePrev.RightButton == ButtonState.Released) rightButtonLastPressed = gameTime.TotalGameTime;
            }
        }

        bool isKeyPressed(Keys key)
        {
            return kStatePrev.IsKeyDown(key) && kState.IsKeyUp(key);
        }

        void resetKeyboardState()
        {
            homeKeyPressed = false;

            f2KeyPressed = false;
            f5KeyPressed = false;

            aKeyPressed = false;

            pageDownPressed = false;
            pageUpPressed = false;

            tabKeyPressed = false;

            plusKeyPressed = false;
            minusKeyPressed = false;

            mKeyPressed = false;
            nKeyPressed = false;
            lKeyPressed = false;
            kKeyPressed = false;
        }

        void updateKeyboardState(GameTime gameTime)
        {
            resetKeyboardState();

            if (isKeyPressed(Keys.Home))
                homeKeyPressed = true;
            if (isKeyPressed(Keys.PageDown))
                pageDownPressed = true;
            if (isKeyPressed(Keys.PageUp))
                pageUpPressed = true;
            if (isKeyPressed(Keys.A))
                aKeyPressed = true;
            if (isKeyPressed(Keys.F5))
                f5KeyPressed = true;
            if (isKeyPressed(Keys.F2))
                f2KeyPressed = true;
            if (isKeyPressed(Keys.Tab))
                tabKeyPressed = true;
            if (isKeyPressed(Keys.M))
                mKeyPressed = true;
            if (isKeyPressed(Keys.N))
                nKeyPressed = true;
            if (isKeyPressed(Keys.L))
                lKeyPressed = true;
            if (isKeyPressed(Keys.K))
                kKeyPressed = true;
        }

        Agent getNearestMouseTarget()
        {
            Vector3 nearPoint = core.GraphicsDevice.Viewport.Unproject(new Vector3((float)mState.X, (float)mState.Y, 0.0f), core.camera.getProjectionMatrix(), core.camera.getViewMatrix(), Matrix.Identity);
            Vector3 farPoint = core.GraphicsDevice.Viewport.Unproject(new Vector3((float)mState.X, (float)mState.Y, 1.0f), core.camera.getProjectionMatrix(), core.camera.getViewMatrix(), Matrix.Identity);

            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();
            Ray ray = new Ray(nearPoint, direction);

            Agent nearestAgent = null;
            float t = float.MaxValue;
            for (int i = 0; i < core.agentList.Count; i++)
            {
                BoundingSphere sphere = core.agentList[i].getBoundingSphere();
                Nullable<float> result = ray.Intersects(sphere);
                if (result.HasValue && result.Value < t)
                {
                    nearestAgent = core.agentList[i];
                    t = result.Value;
                    break;
                }
            }

            return nearestAgent;
        }

        void clearSelection()
        {
            foreach (Agent _agent in selectionTargetList)
                _agent.deselect();
            selectionTargetList.Clear();
            core.camera.unfollow();
        }

        void selectSingleAgent(Agent agent)
        {
            clearSelection();
            selectionTargetList.Add(agent);
            agent.select();
        }

        void selectAllAgents()
        {
            clearSelection();
            foreach (Agent agent in core.agentList)
            {
                selectionTargetList.Add(agent);
                agent.select();
            }
        }

        void selectFirstAgent()
        {
            selectSingleAgent(core.agentList[0]);
        }

        bool selectionExists()
        {
            return selectionTargetList.Count > 0;
        }

        public override void Update(GameTime gameTime)
        {
            if (!core.IsActive) return;

            mStatePrev = mState;
            kStatePrev = kState;
            mState = clone(Mouse.GetState());
            kState = clone(Keyboard.GetState());


            updateMouseState(gameTime);
            updateKeyboardState(gameTime);

            //Mouse
            if (leftButtonClicked)
            {
                Agent nearestAgent = getNearestMouseTarget();
                if (nearestAgent == null)
                    clearSelection();
                else
                    selectSingleAgent(nearestAgent);
            }

            if (leftButtonPressed)
            {
                if (selectionExists()) core.camera.unfollow();
                core.camera.changeCameraOffset(mState.Y - mStatePrev.Y, mState.X - mStatePrev.X);
            }

            if (leftButtonDoubleClicked)
                if (selectionExists())
                {
                    Agent nearestAgent = getNearestMouseTarget();
                    if(selectionTargetList.Contains(nearestAgent))
                        core.camera.follow(nearestAgent);
                }

            if (rightButtonClicked)
                if (selectionExists())
                {
                    DateTime current = DateTime.Now;
                    core.navmesh.mouseRightClicked(selectionTargetList.ToArray(), mState.X, mState.Y);
                    pathfindingTime = DateTime.Now - current;
                }

            if (rightButtonPressed)
                core.camera.changeCameraAngle(mState.Y - mStatePrev.Y, mState.X - mStatePrev.X, 0);

            if (mState.ScrollWheelValue != mStatePrev.ScrollWheelValue)
                core.camera.changeCameraAngle(0, 0, mStatePrev.ScrollWheelValue - mState.ScrollWheelValue);

            //keyboard
            if (homeKeyPressed)
            {
                Agent targetAgent = core.agentList[0];
                selectSingleAgent(targetAgent);
                core.camera.follow(targetAgent);
            }

            if (aKeyPressed)
            {
                selectAllAgents();
                core.camera.follow(selectionTargetList[0]);
            }

            if (pageDownPressed)
            {
                if (selectionExists())
                {
                    int i = core.agentList.IndexOf(selectionTargetList[0]);
                    i = (core.agentList.Count - 1 == i) ? 0 : i + 1;
                    selectSingleAgent(core.agentList[i]);
                    core.camera.follow(core.agentList[i]);
                }
            }

            if (pageUpPressed)
            {
                if (selectionExists())
                {
                    int i = core.agentList.IndexOf(selectionTargetList[0]);
                    i = (i == 0) ? core.agentList.Count - 1 : i - 1;
                    selectSingleAgent(core.agentList[i]);
                    core.camera.follow(core.agentList[i]);
                }
            }

            if (f5KeyPressed)
            {
                core.navmesh.loadNavmesh();
            }

            if (f2KeyPressed)
            {
                core.navmesh.toggleVertexInfoDisplay();
            }

            if (tabKeyPressed)
            {
                displayInformation = !displayInformation;
            }

            if (mKeyPressed)
            {
                core.addAgent();
            }

            if (nKeyPressed)
            {
                core.removeAgent();
            }

            if (lKeyPressed)
            {
                core.toggleMoveAgentsRandomly();
            }

            if (kKeyPressed)
            {
                Navmesh.reactionEnabled = !Navmesh.reactionEnabled;
            }
            /*
            if (kState.IsKeyDown(Keys.S))
            {
                if (selectionActivated)
                    selectionTarget.moveTowards(new Vector3(0.0f, 0.0f, 1.0f));
                directionKeyPressed = true;
            }
            else if (kState.IsKeyDown(Keys.W))
            {
                if (selectionActivated)
                    selectionTarget.moveTowards(new Vector3(0.0f, 0.0f, -1.0f));
                directionKeyPressed = true;
            }
            else if (kState.IsKeyDown(Keys.A))
            {
                if (selectionActivated)
                    selectionTarget.moveTowards(new Vector3(-1.0f, 0.0f, 0.0f));
                directionKeyPressed = true;
            }
            else if (kState.IsKeyDown(Keys.D))
            {
                if (selectionActivated)
                    selectionTarget.moveTowards(new Vector3(1.0f, 0.0f, 0.0f));
                directionKeyPressed = true;
            }
            else
            {
                /*if (selectionActivated && directionKeyPressed)
                {
                    selectionTarget.stop();
                    directionKeyPressed = false;
                }
            }*/
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            if (displayInformation && gameTime.TotalGameTime.TotalMilliseconds - displayInformationLastChecked > Config.Interaction.INFO_CHECK_PERIOD)
            {
                fps = 1000/gameTime.ElapsedGameTime.TotalMilliseconds;

                displayInformationLastChecked = (int) gameTime.TotalGameTime.TotalMilliseconds;
            }

            if (displayInformation)
            {
                spriteBatch.Begin();
                Vector2 position = Config.Interaction.INFO_POS;
                spriteBatch.DrawString(spriteFont, String.Format("{0:0.###} ms to find path", pathfindingTime.TotalMilliseconds), position, Color.White);
                spriteBatch.DrawString(spriteFont, String.Format("{0:0} fps", fps), position += Config.Interaction.LINE_DIFFERENCE, Color.White);
                spriteBatch.DrawString(spriteFont, String.Format("{0:0} agents", core.agentList.Count), position += Config.Interaction.LINE_DIFFERENCE, Color.White);
                spriteBatch.DrawString(spriteFont, String.Format("(L) {0}assigning random paths", core.moveRandomly?"":"stopped "), position += Config.Interaction.LINE_DIFFERENCE, Color.White);
                spriteBatch.DrawString(spriteFont, String.Format("(K) {0} proximity reactions", Navmesh.reactionEnabled ? "enabled" : "disabled"), position += Config.Interaction.LINE_DIFFERENCE, Color.White);
                spriteBatch.End();
            }

            base.Draw(gameTime);
        }
    }
}
